package com.crm.generic.dao.hibernate;

import java.io.Serializable;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Example;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import com.crm.generic.dao.pojo.ValueObject;


/**
 * GenericHibernateDao : it's a generic class, where can the all DAO class extends from it
 * And it help to create a simple DAO program ....
 * @author <a href="mailto:bourogaam@gmail.com">Mohamed BOUROGAA</a>
 *
 * @param <T>  : the generic object, must be extend from the ValueObject
 * @param <ID> : the id type embeded in the ValueObject 
 * @since Juin 28 2008
 */

public class GenericHibernateDao <T extends ValueObject, ID extends Serializable> extends HibernateDaoSupport  implements IGenericDAO <T,ID> {
	
	// extends HibernateDaoSupport 
	/**
	 * Logger for this class
	 */
	private static final Log LOG = LogFactory.getLog(GenericHibernateDao.class);

	private Class<T> persistentClass;
	private boolean cacheQuery = false;

	/**
	 * Constructor
	 * 
	 */
	public GenericHibernateDao() {
		super();
	}

	/**
	 * Constructor.
	 * 
	 * @param persistentClass1
	 * @param cacheQuery1
	 */
	public GenericHibernateDao(final Class<T> persistentClass1,
			final boolean cacheQuery1) {
		super();
		this.persistentClass = persistentClass1;
		this.cacheQuery = cacheQuery1;
	}

	/**
	 * Sets the persistentClass.
	 * 
	 * @param persistentClass1
	 *            The persistentClass to set.
	 */
	public void setPersistentClass(final Class<T> persistentClass1) {
		this.persistentClass = persistentClass1;
	}

	/**
	 * Returns the cacheQuery
	 * 
	 * @return The cacheQuery to return.
	 */
	public boolean isCacheQuery() {
		return this.cacheQuery;
	}

	/**
	 * Sets the cacheQuery.
	 * 
	 * @param cacheQuery1
	 *            The cacheQuery to set.
	 */
	public void setCacheQuery(final boolean cacheQuery1) {
		this.cacheQuery = cacheQuery1;
	}

	/**
	 * getById
	 * 
	 * @param id : La clee primaire de l'entite que l'on doit renvoyer
	 * @return T L'entite qui correspond a la clee primaire demandee, ou null si aucune ne resoud ce critere.
	 */
	@SuppressWarnings("unchecked")
	public T getById(final ID id) {
		return (T) this.getHibernateTemplate().get(this.persistentClass, id);
	}

	/**
	 * findById
	 * 
	 * @param id
	 * @param joinSizeFetchMode
	 * @return T
	 * 
	 */
	@SuppressWarnings("unchecked")
	public T findById(final ID id, final Integer joinSizeFetchMode) {
		final List<T> results = this.findByCriteria(joinSizeFetchMode, Example
				.create(this.getHibernateTemplate().get(this.persistentClass,
						id)));
		if ((results != null) && (results.size() == 1)) {
			return results.get(0);
		} else {
			if (LOG.isErrorEnabled()) {
				LOG.error("ERROR in findById, results=" + results);
			}
			throw new IncorrectResultSizeDataAccessException(0);
		}
	}

	/**
	 * findAll : find all <T> persistance.
	 * 
	 * @return List<T>
	 * 
	 */
	@SuppressWarnings("unchecked")
	public List<T> findAll() {
		return this.findByCriteria();
	}

	/**
	 * findAll
	 * 
	 * @param joinSizeFetchMode
	 * 
	 * @return List<T>
	 * 
	 */
	@SuppressWarnings("unchecked")
	public List<T> findAll(final Integer joinSizeFetchMode) {
		return this.findByCriteria(joinSizeFetchMode);
	}

	/**
	 * findByExample
	 * 
	 * @param exampleInstance
	 * @return List<T>
	 * 
	 */
	@SuppressWarnings("unchecked")
	public List<T> findByExample(final T exampleInstance)

	{
		return findByCriteria(Example.create(exampleInstance));
	}

	/**
	 * findByExample
	 * 
	 * @param exampleInstance
	 * @param joinSizeFetchMode
	 * @return List<T>
	 * 
	 */
	@SuppressWarnings("unchecked")
	public List<T> findByExample(final T exampleInstance,
			final Integer joinSizeFetchMode)

	{
		return findByCriteria(joinSizeFetchMode, Example
				.create(exampleInstance));
	}

	/**
	 * update
	 * 
	 * @param entity
	 * 
	 */
	@SuppressWarnings("unchecked")
	public void update(final T entity) {
		this.getHibernateTemplate().update(entity);
	}

	/**
	 * save
	 * 
	 * @param entity
	 * @return T
	 * 
	 */
	@SuppressWarnings("unchecked")
	public void save(final T entity) {
		this.getHibernateTemplate().save(entity);
	}

	/**
	 * merge
	 * 
	 * @param entity
	 * @return T
	 * 
	 */
	@SuppressWarnings("unchecked")
	public void merge(final T entity) {
		this.getSession(true).merge(entity);
	}

	/**
	 * saveOrUpdate
	 * 
	 * @param entity
	 * @return T
	 * 
	 */
	@SuppressWarnings("unchecked")
	public void saveOrUpdate(final T entity){
		this.getHibernateTemplate().saveOrUpdate(entity);
	}

	/**
	 * delete
	 * 
	 * @param entity
	 * 
	 */
	public void delete(final T entity) {
		this.getHibernateTemplate().delete(entity);
	}

	/**
	 * Use this inside subclasses as a convenience method.
	 * 
	 * @param criterion
	 * @return List<T>
	 * 
	 */
	@SuppressWarnings("unchecked")
	private List<T> findByCriteria(final Criterion... criterion)

	{
		return this.findByCriteria(0, criterion);
	}

	/**
	 * Use this inside subclasses as a convenience method.
	 * 
	 * @param joinSizeFetchMode
	 * @param criterion
	 * @return List<T>
	 * 
	 */
	@SuppressWarnings("unchecked")
	private final List<T> findByCriteria(final Integer joinSizeFetchMode,
			final Criterion... criterion) {
		List<T> list = null;
		Session session = null;
		try {
			session = this.getSession(true);
			final Criteria criteria = session
					.createCriteria(this.persistentClass);
			for (final Criterion c : criterion) {
				criteria.add(c);
				if (LOG.isDebugEnabled()) {
					LOG.debug("criteria.add(c)=" + c);
				}
			}

			/*if (joinSizeFetchMode != null && joinSizeFetchMode.intValue() > 0) {
				final List<String> domainJoinList = retrieveDomainJoinList(joinSizeFetchMode);
				for (final String domainJoin : domainJoinList) {
					criteria.setFetchMode(domainJoin, FetchMode.JOIN);
				}
				// it is not possible to cache query with fetchMode=JOIN )
			} else {
				criteria.setCacheable(this.isCacheQuery());
			}*/
			criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
			list = criteria.list();
		} catch (final HibernateException ex) {
			throw convertHibernateAccessException(ex);
		} finally {
			if (session != null) {
				this.releaseSession(session);
			}
		}
		return list;
	}

	/**
	 * Returns the persistentClass.
	 * 
	 * @return The persistentClass to return.
	 */
	protected Class<T> getPersistentClass() {
		return this.persistentClass;
	}

	/**
	 * 
	 * SessionFlush. Permet de flusher la session, c'est a dire faire un commit,
	 * sans attendre la fermeture de la session...
	 * 
	 */
	public void SessionFlush() {
		Session session = null;
		try {
			session = this.getSession();
			session.flush();
		} catch (final HibernateException ex) {
			throw convertHibernateAccessException(ex);
		} finally {
			if (session != null) {
				this.releaseSession(session);
			}
		}
	}

}
